#pragma once
#ifndef LIT__SCRIPT__MODULE__HH__
#define LIT__SCRIPT__MODULE__HH__

#include "LitScriptPrereqs.h"
#include <LitStream.h>
#include <LitFilePath.h>

namespace Lit
{
	class ScriptEngine;
	class ScriptContext;
	class ScriptIncludeList
	{
	public:
		typedef std::vector<FilePath> vec_t;

	protected:
		vec_t m_inc;

	public:
		ScriptIncludeList(){}
		ScriptIncludeList( const ScriptIncludeList& rk ){addIncludes( rk );}
		~ScriptIncludeList(){}
		
		vec_t&			getIncludes(){ return m_inc; }
		const vec_t&	getIncludes() const{ return m_inc; }
		
		void clear(){ m_inc.clear(); }
		void addIncludes( const vec_t &str )
		{
			for( auto i=str.begin(); i!=str.end(); i++ )
				if( !hasInclude( *i ) )
					m_inc.push_back( *i );
		}
		void addIncludes( const ScriptIncludeList& rk){ addIncludes( rk.m_inc ); }

		void addInclude( const FilePath &str ){ if( !hasInclude( str ) ) m_inc.push_back(str); }
		void removeInclude( const FilePath &str ){ if( hasInclude( str ) ) m_inc.erase(findInclude(str)); }

		bool hasInclude( const FilePath &str )const{ for( auto i=m_inc.begin();i!=m_inc.end(); i++ ){ if( i->str() == str.str() ) return true; }return false; }

		vec_t::iterator			findInclude( const FilePath &str ){ for( auto i=m_inc.begin();i!=m_inc.end(); i++ ) if( i->str() == str.str() ) return i; return m_inc.end(); }
		vec_t::const_iterator	findInclude( const FilePath &str ) const{ for( auto i=m_inc.begin();i!=m_inc.end(); i++ ) { if( i->str() == str.str() ) return i; }return m_inc.end(); }
	};
	class ScriptByteStream : public asIBinaryStream, public Lit::Stream
	{
		size_t mSize;
	public:
		ScriptByteStream(const std::string &fname="", bool read_access = true )
			: Lit::Stream( fname, read_access?BinaryRead:BinaryWrite )
		{
			f = NULL;
			if( !fname.empty() )
			{
				open( fname, read_access?BinaryRead:BinaryWrite );
			}
		}
		virtual ~ScriptByteStream(){
			if( f ) fclose(f);
		}
		
		virtual size_t size() const{ return mSize; }
		virtual bool open( const std::string &fname, Access access = BinaryRead ){
			std::string cmod = "rb";
			if( access == Stream::BinaryRead ) cmod = "rb";
			else if( access == Stream::BinaryWrite ) cmod = "wb";
			else if( access == Stream::TextRead ) cmod = "r";
			else if( access == Stream::TextWrite ) cmod = "w";
			f = fopen( fname.c_str(), cmod.c_str() );
			if( !f ) throw std::runtime_error(("Cannot open binary stream '"+fname+"'").c_str());
			else
			{
				fseek( f, 0, SEEK_END );
				mSize = ftell( f );
				fseek( f, 0, SEEK_SET );
			}
			return true;
		}
		virtual void close(){ if( f ) fclose(f); }
		virtual Stream& read( size_t num_chars, std::string & out ){
			Read( &out.at(0), num_chars*sizeof(char) );
			return *this;
		}
		virtual Stream& write( const std::string& str ){
			Write( &str.at(0), str.size()*sizeof(char) );
			return *this;
		}
		virtual Stream& readUntil( char c, std::string& out = std::string() ){
			std::string cs;
			while( !eof() ){
				read( 1, cs );
				out += cs;
				if( cs[0] == c )
					return *this;
			}
			return *this;
		}
		virtual bool eof() const{ return feof(f); }
		virtual bool valid() const{ return f!= NULL && !eof(); }

		void Write(const void *ptr, asUINT size) 
		{
			if( size == 0 || !f ) return; 
			fwrite(ptr, size, 1, f); 
		}
		void Read(void *ptr, asUINT size) 
		{ 
			if( size == 0 || !f ) return; 
			fread(ptr, size, 1, f); 
		}

	protected:
		FILE *f;
	};
	typedef RefPtr<ScriptByteStream> ScriptByteStreamPtr;
	class ScriptModule
	{
	public:
		typedef asIScriptModule		Type;
		typedef RefPtr<Type>		Ptr;
		typedef ScriptModuleFlags	Flags;

	protected:
		ScriptIncludeList		mIncludes;
		std::string				mName;
		Ptr						mModule;
		RefPtr<ScriptEngine>	mEngine;
		Flags					mFlags;
		size_t					mGlobalLine;
		ScriptPreprocessorPtr	mPreprocessor;

	public:
		ScriptModule(RefPtr<ScriptEngine> engine, const std::string &name, Flags flags=SMF_ALWAYS_CREATE);
		ScriptModule(const ScriptModule& rhs);
		~ScriptModule(void);
		
		void releasePreprocessor();
		void setPreprocessor( ScriptPreprocessorPtr proc );
		void setDefaultPreprocessor();
		
		bool _include( const FilePath& path );
		void _define( const std::string&name, const std::string &value );

		bool hasInclude( const FilePath& p ) const{ return mIncludes.hasInclude(p.str()); }
		void clearIncludes(){ mIncludes.clear(); }

		ScriptIncludeList& getIncludes(){ return mIncludes; }
		const ScriptIncludeList& getIncludes() const{ return mIncludes; }
		
		void saveByteCode( ScriptByteStreamPtr );
		void loadByteCode( ScriptByteStreamPtr );


		std::string		getName() const;
		Flags			getFlags() const;

		const Ptr& data() const;
		Ptr& data();

		ScriptContextState execute(const std::string &entry_point, RefPtr<ScriptContext> context );

		const RefPtr<ScriptEngine>& getEngine() const;
		RefPtr<ScriptEngine>& getEngine();
		
		int addSection( const std::string &name, const std::string &content );
		int build();
	};
};

#endif